
;用户程序对于本程序而言只是一堆数据而已,因此可以将ds和es指向用户程序的开始，而方便访问用户程序
start_sector equ 100 ;用户程序在硬盘上的起始扇区

section myCode align=16 vstart=0x7c00

					;设置ds和es的值，让ds和es都指向用户程序的开始,[ds:0]代表的是用户程序开始,[es:0]代表用户程序的开始
					mov bx, phy_loc_user ;物理地址必须是16位对齐的，也就是16进制的最后一位是0，能被16整除
					mov ax, [cs:bx] ;物理地址的低16位
					mov dx, [cs:bx + 2] ;物理地址的高4位,物理地址总共是20位，需要4个字节来存放
					mov bx, 16
					div bx ;除法，16位的除法，ax存储商，dx存储余数。除以16相当于右移4位
					mov ds, ax
					mov es, ax

					;开始读取一个扇区
					mov di, start_sector
					mov si, 0
					mov bx, 0
					call read_one_sector

					;获取用户程序的长度，计算用户程序占据几个扇区
					mov ax, [0] ;用户程序长度的低16位
					mov dx, [2] ;用户程序长度的高16位
					mov bx, 512
					div bx ;除法，16位的除法，ax存储商，dx存储余数
					cmp dx, 0
					jne read_surplus_user
					dec ax ;余数为0，代表正好整除，但是已经读了一个扇区了，所以剩下的扇区数需要减1


;读取用户程序剩余的扇区
;ax中存储的是扇区数
read_surplus_user:	

					cmp ax, 0
					je reloc_user_code_entry ;等于0代表用户程序在硬盘上面只占据一个扇区，前面已经读过了一个扇区，不用再读了
					push ds ;保存ds
					mov di, start_sector
					mov cx, ax
loop_read_1:		add di, 0x01
					adc si, 0
					mov ax, ds
					add ax, 0x20
					mov ds, ax ;每次ds+0x20,bx设置为0，相当于是加了512个字节,在内存上面还是连续的。 0x200 = 512 
					mov bx, 0
					call read_one_sector
					loop loop_read_1

					pop ds ;恢复ds


;重定位用户入口段的段地址
reloc_user_code_entry:
					mov ax, [6] ;[ds:0]代表的是用户程序开始
					mov dx, [8]
					call calculate_user_base_segment
					mov [6], ax ;用计算出来的段地址回填

;重定位用户程序中定义的段
reloc_user_segment:
					mov cx, [0x0a] ;读取重定位表项的个数
					mov bx, 0x0c
reloc_1:			mov ax, [bx]
					mov dx, [bx + 2]
					call calculate_user_base_segment
					mov [bx], ax ;用计算出来的段地址回填
					add bx, 4
					loop reloc_1

					;跳转到用户程序
					jmp far [4] ;[4]中为偏移，[6]中为段地址,段地址为已经重定位过了的



;计算用户程序里面段的段地址
;dx代表汇编地址的高16位
;ax代表汇编地址的低16位
;输出时，ax代表计算结果
calculate_user_base_segment:
					push bx
					
					add ax, [cs:phy_loc_user]
					adc dx, [cs:phy_loc_user + 2]
					mov bx, 16
					div bx ;ax存储的是商

					pop bx
					ret




;读取一个扇区的子程序，硬盘的逻辑扇区使用的是LBA28，28位代表一个逻辑扇区号，此处用4个字节
;si代表读取的起始扇区的高16位
;di代表读取的起始扇区的低16位
;读取到的内容放到内存的 [ds:bx]处
read_one_sector:
					push dx
					push ax
					push cx

					;设置要读取的扇区数,本函数只读取一个扇区
					mov dx, 0x1f2
					mov al, 0x1 ;8位端口
					out dx, al

					;设置起始逻辑扇区号, 0x1f3 0x1f4 0x1f5 0x1f6分别存0-7 8-15 16-23 24-27
					push bx ;bx是参数，后面还有用处的，此处需要先保存
					mov bx, di ;因为di不可分割,不可分割就无法取出里面的高8位和低8位
					mov dx, 0x1f3
					mov al, bl
					out dx, al

					mov dx, 0x1f4
					mov al, bh
					out dx, al

					mov bx, si
					mov dx, 0x1f5
					mov al, bl
					out dx, al

					mov dx, 0x1f6
					and bh, 0x0f ;bh的高4位置0,0f = 00001111
					or bh, 0xe0 ;e0 = 11100000
					mov al, bh
					out dx, al
					pop bx ;恢复bx

					;请求硬盘读,向0x1f7写入0x20,这也是一个8位端口
					mov dx, 0x1f7
					mov al, 0x20
					out dx, al

					;等待读写完成,读0x1f7端口，查看状态
					mov dx, 0x1f7
wait_disk_read:		in al, dx
					and al, 0x88 ;0x88 = 10001000
					cmp al, 0x08 ;0x08 = 00001000
					jne wait_disk_read


					;读数据，端口为0x1f0,该端口为16位端口
					mov dx, 0x1f0
					mov cx, 256
disk_read:			in ax, dx
					mov [bx], ax
					add bx, 2
					loop disk_read


					pop cx
					pop ax
					pop dx
					ret


phy_loc_user		dd 0x10000	;用户程序加载到内存里面的起始位置	


times 510-($-$$)	db 0
dw 0xaa55		



